﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Sockets;
using Apache.Cassandra;
using SKBKontur.Cassandra.Proxy.Selectors;

namespace SKBKontur.Cassandra.Proxy
{
    public class MultiTry : IStorage
    {
        private readonly IStorage storage;
        private readonly IMultiTryManager manager;

        public MultiTry(IStorage storage, IMultiTryManager manager)
        {
            this.storage = storage;
            this.manager = manager;
        }

        #region IStorage Members

        public void Dispose()
        {
            storage.Dispose();
        }

        public void Insert(byte[] key, ColumnParent columnParent, ConsistencyLevel consistencyLevel,
                           params Column[] columns)
        {
            SafeCall(StorageOperation.Insert, delegate(IStorage t)
            {
                t.Insert(key, columnParent, consistencyLevel, columns);
                return true;
            });
        }

        public Dictionary<byte[], List<ColumnOrSuperColumn>> MultigetSlice(IEnumerable<byte[]> keys,
                                                                           ColumnParent columnParent,
                                                                           SlicePredicate slicePredicate,
                                                                           ConsistencyLevel consistencyLevel)
        {
            return SafeCall(StorageOperation.MultigetSlice, t => t.MultigetSlice(keys, columnParent, slicePredicate, consistencyLevel));
        }

        public List<ColumnOrSuperColumn> GetSlice(byte[] key, ColumnParent columnParent, SlicePredicate slicePredicate,
                                                  ConsistencyLevel consistencyLevel)
        {
            return SafeCall(StorageOperation.GetSlice, t => t.GetSlice(key, columnParent, slicePredicate, consistencyLevel));
        }

        public List<KeySlice> GetIndexedSlice(ColumnParent columnParent, IndexClause indexClause, SlicePredicate column_predicate, ConsistencyLevel consistencyLevel)
        {
            return SafeCall(StorageOperation.GetIndexedSlice, t => t.GetIndexedSlice(columnParent, indexClause, column_predicate, consistencyLevel));
        }

        #endregion

        private T SafeCall<T>(StorageOperation storageOperation, Func<IStorage, T> function)
        {
            manager.Begin();

            while (true)
            {
                Stopwatch stopWatch = Stopwatch.StartNew();
                try
                {
                    var result = function(storage);
                    manager.SuccessOp(storageOperation, stopWatch.ElapsedMilliseconds);
                    return result;
                }
                catch (Exception e)
                {
                    if (!IsRelevantException(e) || !manager.CanContinue(storageOperation, stopWatch.ElapsedMilliseconds, e))
                        throw;
                }
            }
        }

        private static bool IsRelevantException(Exception exception)
        {
            Type type = exception.GetType();
            return
                type.Equals(typeof(TimedOutException)) |
                type.Equals(typeof(UnavailableException)) |
                type.Equals(typeof(SocketException));
            // todo неправильный список ексепшенов
        }
    }
}
